/**
 * Copyright (C) 2008 Huascar A. Sanchez.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.gotobject.inject.resources;

import com.google.inject.Binder;
import com.google.inject.Key;
import static com.gotobject.inject.util.Utils.cast;
import static com.gotobject.inject.util.Utils.newHashMap;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;

/**
 * <p>
 * This class reads a resource file and convert any resource definition into something that
 * {@link com.google.inject.Guice Guice} can understand, so that bindings out of those resource
 * definitions could be created.
 * </p>
 *
 * <p>
 * The main elements utilized by this class are: a {@link ResourceType resource type} descriptor
 * and an implementation of the {@link Resource resource annotation} called
 * {@link ResourceImpl}.
 * </p>
 *
 * <p>
 * The {@code Resources} supports three use cases:
 * <dl>
 * <dt>Use Case I:
 * <dd>The loading of resources from a properties file using
 * {@link #from(java.util.Properties, com.google.inject.Binder)}.
 * <dt>Use Case II:
 * <dd>The loading of resources from a map object using
 * {@link #from(java.util.Map, com.google.inject.Binder)}.
 * <dt> Use Case III:
 * <dd>The loading of resources from an {@link Resource annotation} using
 * {@link #from(Resource, String, com.google.inject.Binder)}.
 * </p>
 *                                                        
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class Resources {
    private Resources(){}

    private static void bindColors(Map<String, String> colors, Binder toBinder) {
        for(Map.Entry<String,String> eachEntry : colors.entrySet()){
            // .. appended values, separated by comma
            final String[] rgb = givenEntire(eachEntry.getValue(), ",");

            // .. individual values.
            final int r = Integer.valueOf(rgb[0]);
            final int g = Integer.valueOf(rgb[1]);
            final int b = Integer.valueOf(rgb[2]);

            toBinder.bind(Key.get(
                    Color.class,
                    new ResourceImpl(eachEntry.getKey()
                    ))).toInstance(
                    new Color(r, g, b)
            );
        }
    }

    private static void bindFiles(Map<String, String> files, Binder toBinder) {
        for(Map.Entry<String,String> eachEntry : files.entrySet()){
            toBinder.bind(Key.get(
                    File.class,
                    new ResourceImpl(eachEntry.getKey()
                    ))).toInstance(
                    new File(eachEntry.getValue())
            );
        }
    }


    private static void bindImages(Map<String, String> images, Binder toBinder){
        for(Map.Entry<String,String> eachEntry : images.entrySet()){
            try {
                toBinder.bind(Key.get(Image.class, new ResourceImpl(eachEntry.getKey()))).toInstance(
                        Image.class.cast(ImageIO.read(new File(eachEntry.getValue())))
                );
            } catch (IOException e) {
                toBinder.addError(e);
            }
        }
    }

    /**
     * wanting to annotate individual resources? well, there you go. This method
     * will allow users that don't want to use the {@link ResourceModule dynamic resource loading}
     * mechanism and want to control what they are binding.
     * @param annotation
     *      a {@link com.gotobject.inject.resources.Resource configured annotation}
     * @param value
     *      the actual value of the resource that we will be binding to the given annotation.
     * @param binder
     *      {@link com.google.inject.Binder Guice's binder}
     */
    public static void from(Resource annotation, String value, Binder binder){
        makeBindingOutOf(annotation.key(), value, binder);
    }

    /**
     * binds a set of resources defined in a map object using
     * the traditional Guice {@link Binder binder} object.
     * @param props a map file containing inject's
     *      definitions.
     * @param binder a Guice {@link Binder binder}.
     */
    public static void from(Map<String, String> props, Binder binder) {
        for (Map.Entry<String, String> entry : props.entrySet()) {
            final String key    = entry.getKey();
            final String value  = entry.getValue();

            // .. define resources
            makeBindingOutOf(key, value, binder);
        }
    }

    /**
     * binds a set of resources defined in a properties file using
     * the traditional Guice {@link Binder binder} object.
     * @param props a properties file containing a set of resources's
     *      definition.
     * @param binder a Guice {@link Binder binder}.
     */
    public static void from(Properties props, Binder binder){
        for(Map.Entry<?, ?> eachEntry : props.entrySet()){
            final String key    = cast(eachEntry.getKey(), String.class);
            final String value  = cast(eachEntry.getValue(), String.class);

            // .. define resources
            makeBindingOutOf(key, value, binder);
        }
    }

    private static String givenBrief(String aKey){
        return givenEntire(aKey, ".")[1];
    }

    private static String[] givenEntire(String value, String usingSeparator){
        return value.split("\\" + usingSeparator);
    }

    private static void makeBindingOutOf(String key, String value, Binder binder){
        final ResourceType type  = ResourceType.from(givenBrief(key));
        type.put(key, value);
        switch (type){
            case IMAGE:
                bindImages(type.getData(), binder);
                break;
            case COLOR:
                bindColors(type.getData(), binder);
                break;
            case FILE:
                bindFiles(type.getData(), binder);
                break;
            default: throw new IllegalArgumentException("Unsupported inject type.");
        }
    }

    /**
     *
     * @param key
     *      resource indentifier following this notation: keyName.resourceType. i.e.,
     *      refcard.image
     * @return a {@link Resource configured annotation}
     */
    public static Resource tagged(String key){
        return new ResourceImpl(key);
    }


    /**
     * the supported {@link ResourceType resource types} by this framework.
     */
    private enum ResourceType {
        COLOR("color"),
        IMAGE("image"),
        FILE("file");

        private final String label;
        private final Map<String, String> segment;
        ResourceType(String label){
            this.label = label;
            segment    = newHashMap();
        }

        /**
         * takes a particular {@code label} and returns the appropiate
         * {@link ResourceType resource type}
         * @param label
         *     {@link ResourceType resource type}'s label.
         * @return a {@link ResourceType resource type}
         */
        static ResourceType from(String label){
            final ResourceType[] inTypes = values();
            for(ResourceType eachType : inTypes){
                if(eachType.label().equals(label)){
                    return eachType;
                }
            }

            throw new NullPointerException("Element not found. Not found element's name? " + label);
        }

        public Map<String, String> getData(){
            return Collections.unmodifiableMap(segment);
        }

        private String label(){
            return label;
        }

        public void put(String ref, String value){
            segment.put(ref, value);
        }

        @Override
        public String toString(){
            return new StringBuilder("Resource[type=")
                    .append(label())
                    .append("]")
                    .toString();
        }

    }

}
